/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */


/**
 *
 * @author subgroup2
 */
public class Arithmetic {

    int PC;
    int MDR;
    Code ram;
    Register reg;

    Arithmetic(Code ram, Register register) {
        this.ram = ram;
        this.reg = register;
    }

    // complement carry bit
    void cplC() {
        if (reg.getC() == 1) {
            reg.setC(0);
        } else {
            reg.setC(1);
        }
    }

    // Complement accumulator
    void cplA() {
        int[] bits = new int[8];
        bits = this.byte2Bit(reg.getA());
        for (int i = 0; i <= 7; i++) {
            if (bits[i] == 1) {
                bits[i] = 0;
            } else {
                bits[i] = 1;
            }
        }
        reg.setA(this.bit2Byte(bits));
    }

    // Exchange SP with DPTR
    void xcsd() {
        int temp;
        temp = reg.getSP();
        reg.setSP(reg.getDPTR());
        reg.setDPTR(temp);
    }

    private int bit2Byte(int[] b) {
        int byt = (b[0] * 1 + b[1] * 2 + b[2] * 4 + b[3] * 8 + b[4] * 16 + b[5] * 32 + b[6] * 64 + b[7] * 128);
        return byt;
    }

    private int[] byte2Bit(short b) {
        int[] bits = new int[8];
        bits[7] = b / 128;
        bits[6] = (b % 128) / 64;
        bits[5] = (b % 64) / 32;
        bits[4] = (b % 32) / 16;
        bits[3] = (b % 16) / 8;
        bits[2] = (b % 8) / 4;
        bits[1] = (b % 4) / 2;
        bits[0] = (b % 2) / 1;
        return bits;
    }

    private int fetch() {
        PC = reg.getPC();
        reg.increasePC();
        return ram.getRAM(PC);
    }

    void INC_A() {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    // Get data from the direct 16bit-address
    int getDirectData() {
        int temp = this.fetch();
        temp = this.fetch() + temp * 256;
        return ram.getRAM(temp);
    }

    // Set data into the direct 16bit-address
    void setData(short data) {
        int temp = this.fetch();
        temp = this.fetch() + temp * 256;
        ram.setRAM(data, temp);
    }

    // Get data from the relative 16bit-address
    int getRelativeData(short addr1, int addr2) {
        return ram.getRAM(addr1 + addr2);
    }

    // Get data from the indirect 16bit-address
    int getIndirectData() {
        int temp = this.fetch();
        temp = this.fetch() + temp * 256;
        int addr = ram.getRAM(temp)*256 + ram.getRAM(temp+1);  // get address @addr16
        return ram.getRAM(addr);
    }

    // Set data to the indirect 16bit-address
    void setIndirectData(short data) {
        int temp = this.fetch();
        temp = this.fetch() + temp * 256;
        int addr = ram.getRAM(temp)*256 + ram.getRAM(temp+1);  // get address @addr16
        ram.setRAM(data,addr);
    }

    //  Register indirect move from another register or data
    void set2DPTR(short data) {
        int temp = reg.getDPTR();
        ram.setRAM(data, temp);
    }

    // Find out result of data1 AMD data2
    int AND(short data1, short data2) {
        int[] bits1 = new int[8];
        int[] bits2 = new int[8];
        bits1 = this.byte2Bit(data1);
        bits2 = this.byte2Bit(data2);
        for (int i = 0; i <= 7; i++) {
            if (bits1[i] != bits2[i]) {
                bits1[i] = 0;
            }
        }
        return this.bit2Byte(bits1);
    }

    // Find out result of data1 OR data2
    int OR(short data1, short data2) {
        int[] bits1 = new int[8];
        int[] bits2 = new int[8];
        bits1 = this.byte2Bit(data1);
        bits2 = this.byte2Bit(data2);
        for (int i = 0; i <= 7; i++) {
            if (bits1[i] != bits2[i]) {
                bits1[i] = 1;
            }
        }
        return this.bit2Byte(bits1);
    }

    // Find out result of data1 XOR data2
    int XOR(short data1, short data2) {
        int[] bits1 = new int[8];
        int[] bits2 = new int[8];
        bits1 = this.byte2Bit(data1);
        bits2 = this.byte2Bit(data2);
        for (int i = 0; i <= 7; i++) {
            if (bits1[i] == bits2[i]) {
                bits1[i] = 0;
            } else {
                bits1[i] = 1;
            }
        }
        return this.bit2Byte(bits1);

    }

    // Rotate a data8 left
    int RL(short data) {
        int[] bits1 = new int[8];
        int[] bits2 = new int[8];
        bits1 = this.byte2Bit(data);
        bits2[7] = bits1[6];
        bits2[6] = bits1[5];
        bits2[5] = bits1[4];
        bits2[4] = bits1[3];
        bits2[3] = bits1[2];
        bits2[2] = bits1[1];
        bits2[1] = bits1[0];
        bits2[0] = bits1[7];
        return this.bit2Byte(bits2);
    }

    // Rotate a data8 left through carry
    int RLC(short data) {
        int[] bits1 = new int[8];
        int[] bits2 = new int[8];
        bits1 = this.byte2Bit(data);
        bits2[7] = bits1[6];
        bits2[6] = bits1[5];
        bits2[5] = bits1[4];
        bits2[4] = bits1[3];
        bits2[3] = bits1[2];
        bits2[2] = bits1[1];
        bits2[1] = bits1[0];
        bits2[0] = reg.getC();
        reg.setC(bits1[7]);
        return this.bit2Byte(bits2);
    }

    // Rotate a data8 right
    int RR(short data) {
        int[] bits1 = new int[8];
        int[] bits2 = new int[8];
        bits1 = this.byte2Bit(data);
        bits2[7] = bits1[0];
        bits2[6] = bits1[7];
        bits2[5] = bits1[6];
        bits2[4] = bits1[5];
        bits2[3] = bits1[4];
        bits2[2] = bits1[3];
        bits2[1] = bits1[2];
        bits2[0] = bits1[1];
        return this.bit2Byte(bits2);
    }

    // Rotate a data8 right through carry
    int RRC(short data) {
        int[] bits1 = new int[8];
        int[] bits2 = new int[8];
        bits1 = this.byte2Bit(data);
        bits2[7] = reg.getC();
        bits2[6] = bits1[7];
        bits2[5] = bits1[6];
        bits2[4] = bits1[5];
        bits2[3] = bits1[4];
        bits2[2] = bits1[3];
        bits2[1] = bits1[2];
        bits2[0] = bits1[1];
        reg.setC(bits1[0]);
        return this.bit2Byte(bits2);
    }

    // Find out the result of data1 + data2
    public short ADD(short data1, short data2) {
        short result = 0;
        //set the AC flag according to the lower 4 bits for a and b
        if (((data1 % 16) + (data2 % 16)) >= 16) {
            reg.setAC(1);
        } else {
            reg.setAC(0);
        }
        //get the result of a+b
        result = (short) (data1 + data2);
        //call the method flagControl() to set Z and P flag
        this.flagControl(result);
        //set the C flag according to the result
        if (result > 255) {
            reg.setC(1);
        } else {
            reg.setC(0);
        }
        //if S flag is 0, turn to unsigned add regulation
        if (reg.getS() == 0) {
            //set OV flag according to unsigned ADD regulation
            if (((data1 % 128 + data2 % 128) >= 128) && ((data1 % 256 + data2 % 256) < 256)) {
                reg.setOV(1);
            } else if (((data1 % 128 + data2 % 128) < 128) && ((data1 % 256 + data2 % 256) >= 256)) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        } //if S flag is 1, turn to signed add regulation
        else if (reg.getS() == 1) {
            //set OV flag according to signed ADD regulation
            if (((data1 / 128 == 1) && (data2 / 128 == 1) && result == 0) || ((data1 / 128 == 0) && (data2 / 128 == 0) && result == 1)) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        }
        //get the lower 8 bits of the result
        result = (short) (result % 256);
        return result;
    }

    // Find out the result of data1 + data2 + C
    public short ADDC(short data1, short data2) {
        short result = 0;
        //set the AC flag according to the lower 4 bits for a and b
        if (((data1 % 16) + (data2 % 16) + reg.getC()) >= 16) {
            reg.setAC(1);
        } else {
            reg.setAC(0);
        }
        //get the result of a+b+C
        result = (short) (data1 + data2 + reg.getC());
        //call the method flagControl() to set Z and P fla
        this.flagControl(result);
        //set the C flag according to the result
        if (result > 255) {
            reg.setC(1);
        } else {
            reg.setC(0);
        }

        //if S flag is 0, turn to unsigned ADDC regulation
        if (reg.getS() == 0) {
            //set OV flag according to unsigned ADDC regulation
            if (((data1 % 128 + data2 % 128 + reg.getC()) >= 128) && ((data1 % 256 + data2 % 256 + reg.getC()) < 256)) {
                reg.setOV(1);
            } else if (((data1 % 128 + data2 % 128 + reg.getC()) < 128) && ((data1 % 256 + data2 % 256 + reg.getC()) >= 256)) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        } //if S flag is 1, turn to signed ADDC regulation
        else if (reg.getS() == 1) {
            //set OV flag according to signed ADDC regulation
            if (((data1 / 128 == 1) && (data2 / 128 == 1) && result == 0) || ((data1 / 128 == 0) && (data2 / 128 == 0) && result == 1)) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        }
        //get the lower 8 bits of the result
        result = (short) (result % 256);
        return result;
    }

    // Find out the result of data1 - data2
    public short SUB(short data1, short data2) {
        short result = 0;
        //set the AC flag according to the lower 4 bits for a and b
        if (((data1 % 16) - (data2 % 16) < 0)) {
            reg.setAC(1);
        } else {
            reg.setAC(0);
        }
        if ((data1 - data1) < 0){
            result = (short) ((data1+256) - data2);
            reg.setC(1);
        }
        else{
            result = (short) (data1 - data2);
            reg.setC(0);
        }
        //call method flagControl() to set Z and P flag
        this.flagControl(result);
        //if S flag is 0, turn to unsigned SUB regulation
        if (reg.getS() == 0) {
            //set OV flag according to unsigned SUB regulation
            if (((data1 % 128) < (data2 % 128)) && (data1 >= data2)) {
                reg.setOV(1);
            } else if (((data1 % 128) >= (data2 & 128)) && (data1 < data2)) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        } //if S flag is 1, turn to signed SUB regulation
        else if (reg.getS() == 1) {
            //set OV flag according to signed SUB regulation
            if (((data1 / 128 == 0) && (data2 / 128 == 1) && result == 1) || ((data1 / 128 == 1) && (data2 / 128 == 0) && result == 0)) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        }
        //get the lower 8 bits of the result
        result = (short) (result % 256);
        return result;
    }

    // Find out the result of data1 - data2 - C
    public short SUBB(short a, short b) {
        short result = 0;
        //set the AC flag according to the lower 4 bits for a and b
        if (((data1 % 16) - (data2 % 16) - reg.getC() < 0)) {
            reg.setAC(1);
        } else {
            reg.setAC(0);
        }
        if ((data1 - data1 - reg.getC()) < 0){
            result = (short) ((data1+256) - data2 - reg.getC());
            reg.setC(1);
        }
        else{
            result = (short) (data1 - data2 - reg.getC());
            reg.setC(0);
        }
        //call method flagControl() to set Z and P flag
        this.flagControl(result);
        //if S flag is 0, turn to unsigned SUBB regulation
        if (reg.getS() == 0) {
            //set OV flag according to unsigned SUBB regulation
            if (((a % 128) < (b % 128 + reg.getC())) && (a >= (b + reg.getC()))) {
                reg.setOV(1);
            } else if (((a % 128) >= (b & 128 + reg.getC())) && (a < (b + reg.getC()))) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        } //if S flag is 1, turn to signed SUBB regulation
        else if (reg.getS() == 1) {
            //set OV flag according to signed SUBB regulation
            if (((a / 128 == 0) && (b / 128 == 1) && result == 1) || ((a / 128 == 1) && (b / 128 == 0) && result == 0)) {
                reg.setOV(1);
            } else {
                reg.setOV(0);
            }
        }
        //get the lower 8 bits of the result
        result = (short) (result % 256);
        return result;
    }

    // Change flag Z and flag P if needed
    public void flagControl(short result) {
        if (result == 0) //set Z flag
        {
            reg.setZ(1);
        } else {
            reg.setZ(0);
        }
        //wrong
        if (result % 2 == 0) //set P flag
        {
            reg.setP(1);
        } else {
            reg.setP(0);
        }
    }

    /**
     * method used to emulate the instruction PJMP(page jump), PC=PC(15-11) AAA addr8, AAA is the
     * last three bits of the instruction, addr8 is the byte after instruction, after change PC, PC
     * must be saved in the register
     *
     * @see getPC()
     * @see setPC()
     * @see byte2Bit()
     * @param i    temple parameter, instruction use to do the operation
     * @author WEI
     */
    void PJMP(int addr1, short addr2) {
        PC = reg.getPC();
        int tempPC = PC - 2;
        reg.setPCL(addr2);
        reg.setPCH((tempPC / 2048) * 8 + addr1);
    }

    /**
     * method used to emulate the instruction PCALL(page call), PC=PC(15-11) AAA addr8, AAA is the
     * last three bits of the instruction, addr8 is the byte after instruction, after change PC, PC
     * must be saved in the register, before jump, PC was pushed into the stack
     *
     * @see PUSHALL()
     * @see byte2Bit()
     * @see getPC()
     * @see setPC()
     * @param i    temple parameter, the same to instruction, use to do the operation
     */
    void PCALL(int addr1, short addr2) {
        this.pushPC();
        this.PJMP(addr1, addr2);
    }

    void LJMP(short addr1, short addr2) {
        reg.setPCH(addr1);
        reg.setPCL(addr2);
    }

    void LCALL(short addr1, short addr2) {
        this.pushPC();
        reg.setPCH(addr1);
        reg.setPCL(addr2);
    }

    void pushPC() {
        this.PUSH(reg.getPCH());
        this.PUSH(reg.getPCL());
    }

    void popPC() {
        reg.setPCL(this.POP());
        reg.setPCH(this.POP());
    }

    void PUSH(short data) {
        int SP = reg.getSP();
        ram.setRAM(data, SP);
        reg.increaseSP();
    }

    short POP() {
        short data;
        int SP = reg.getSP();
        data = ram.getRAM(SP - 1);
        reg.decreaseSP();
        return data;
    }

    // Unsigned integer multiply R0 by R1, 16-bit result left in R0 and R1, R0 holds most significant byte
    void MUL_R0_R1() {
        //define an integer array a
        int[] a = new int[8];
        //define an integer array b
        int[] b = new int[8];
        int result = 0;
        //get the result of R0 * R1
        result = reg.getR0() * reg.getR1();
        //use array a to save the higher 8 bits of the result
        a[7] = result / 32768;
        a[6] = (result % 32768) / 16384;
        a[5] = (result % 16384) / 8192;
        a[4] = (result % 8192) / 4096;
        a[3] = (result % 4096) / 2048;
        a[2] = (result % 2048) / 1024;
        a[1] = (result % 1024) / 512;
        a[0] = (result % 512) / 256;
        b[7] = (result % 256) / 128;										//use array b to save the lower 8 bits of the result
        b[6] = (result % 128) / 64;
        b[5] = (result % 64) / 32;
        b[4] = (result % 32) / 16;
        b[3] = (result % 16) / 8;
        b[2] = (result % 8) / 4;
        b[1] = (result % 4) / 2;
        b[0] = (result % 2) / 1;
        //call method bit2Byte to change the value in a to short and save it to R0
        reg.setR0(bit2Byte(a));
        //call method bit2Byte to change the value in b to short and save it to R1
        reg.setR1(bit2Byte(b));
    }

    // Unsigned integer division R0 by R1, 16-bit result left in R0 and R1, R0 holds most significant byte
    public void DIV_R0_R1() {
        //define an integer array c
        int[] c = new int[8];
        //define an integer array d
        int[] d = new int[8];
        //used to save the integer part of the result
        int integer = reg.getR0() / reg.getR1();
        //used to save the decimal part of the result
        int decimal = reg.getR0() % reg.getR1();
        //use array c to save the integer part of the result
        c[7] = integer / 128;
        c[6] = (integer % 128) / 64;
        c[5] = (integer % 64) / 32;
        c[4] = (integer % 32) / 16;
        c[3] = (integer % 16) / 8;
        c[2] = (integer % 8) / 4;
        c[1] = (integer % 4) / 2;
        c[0] = (integer % 2) / 1;
        //use array d to save the decimal part of the result
        d[7] = decimal / 128;
        d[6] = (decimal % 128) / 64;
        d[5] = (decimal % 64) / 32;
        d[4] = (decimal % 32) / 16;
        d[3] = (decimal % 16) / 8;
        d[2] = (decimal % 8) / 4;
        d[1] = (decimal % 4) / 2;
        d[0] = (decimal % 2) / 1;
        //call method bit2Byte to change the value in c to short and save it to R0
        reg.setR0(bit2Byte(c));
        //call method bit2Byte to change the value in d to short and save it to R1
        reg.setR1(bit2Byte(d));
    }

    // Decimal adjust accumulator
    public void DA_A() {
        int daa = reg.getA();
        //implement DA A operation for the value in A and save it to A
        if ((daa % 16 >= 9 || reg.getAC() == 1) && daa / 16 >= 9) {
            reg.setA((short) (reg.getA() + 0x66));
        } else if (daa / 16 >= 9) {
            reg.setA((short) (reg.getA() + 0x60));
        } else if (daa % 16 >= 9 || reg.getAC() == 1) {
            reg.setA((short) (reg.getA() + 0x06));
        } else {
            reg.setA((short) (reg.getA() + 0x00));
        }
    }
}
